'''
本方法是基于内容的推荐，目的是生成用户的画像，利用用户评过分的项目的特征分布作一个总结，获得用户在所有特征上的喜好
分布向量，比如一部电影，其特征可能是动作，科幻等。缺点是对一些无法描述的项目不好推荐。优点是独立于用户评分情况，
缓解冷启动问题。
这里使用所有用户喜好特征的电影的平均分作为用户画像，根据这个向量计算与推荐与其最相似的电影
'''
import os
import numpy as np
import pandas as pd
pd.set_option('display.max_columns', None)
from util.similarity import Cosine


class ContentBased():
    # itemsNmaesList是所有项目的名称向量，itemsFeatures是所有项目的特征分布，user_vec是用户评过的项目向量
    def __init__(self, itemsNamesList, itemsFeatures, user_vec):
        self.itemsNamesList = itemsNamesList
        self.itemsFeatures = itemsFeatures
        self.user_vec = user_vec
        self.feature_columns = ["未知", "动作", "冒险", "动画", "儿童",
                                "喜剧", "犯罪", "纪录片", "剧情", "奇幻",
                                "黑色", "恐怖", "音乐剧", "悬疑", "浪漫",
                                "科幻", "惊悚", "战争", "西部"]

    # 计算用户画像与所有其未评过分的项目的相似度
    def calSimilarity(self, user_features, n_items, top_k):
        itemSimlarity = np.zeros(n_items)
        cosSim = Cosine()  # 相似度
        for item in range(n_items):
            if self.user_vec[item] == 0.0:  # 用户未评过分的项目
                itemSimlarity[item] = cosSim.similarity(user_features, self.itemsFeatures[item])
        # 对相似度排序，输出最高相似度的项目，降序排序
        order_item_sim = np.argsort(itemSimlarity)[::-1][0:top_k]  # 这个返回的是索引，前top_k个
        return order_item_sim, itemSimlarity

    # 计算用户画像，推荐与画像最相似的top_k个项目
    def getRecItems(self, top_k):
        """
        # 第0行，多少个特征，这里使用的是电影的类型，也就是
        "未知", "动作", "冒险", "动画", "儿童", "喜剧", "犯罪", "纪录片", "剧情", "奇幻", "黑色", "恐怖", "音乐剧", "悬疑", "浪漫", "科幻", "惊悚", "战争", "西部"
        这19个子类，即特征向量为19维向量
        """
        n_features = len(self.itemsFeatures[0])

        n_items = len(self.user_vec)  # 此用户的item数（也就是所有item数）
        user_max = self.user_vec[self.user_vec > 0.].max()  # 获得该用户对1682部电影评分的最大值
        user_min = self.user_vec[self.user_vec > 0.].min()  # 获得该用户对1682部电影评分的最小值
        diff_uvec = (self.user_vec - user_min)/(user_max - user_min) if (user_max - user_min) > 0.0 else 0.0# 用户的评分做最大最小归一化
        user_features = np.zeros(n_features).astype(float)  # 这里是用户画像，是用户对所有特征的偏好程度
        item_featuers_sum = np.zeros(n_features)  # 这里是暂存包含某个特征的item数量
        for item in range(n_items):
            if self.user_vec[item] > 0:  # 用户评过分的项目
                for feature in range(n_features):
                    user_features[feature] += diff_uvec[item] * self.itemsFeatures[item, feature]
                    item_featuers_sum[feature] += self.itemsFeatures[item, feature]  # 若item包含这个特征，就加1，否则加0
        # 特征平均
        for feature in range(n_features):
            if item_featuers_sum[feature] > 0:
                user_features[feature] = round(user_features[feature] / float(item_featuers_sum[feature]), 2)

        # 得到了用户画像user_features（对所有特征的偏好程度），就可以用它计算与用户未评过分的项目的相似度
        user_feature_dict = sorted(dict(zip(self.feature_columns, user_features.tolist())).items(), key=lambda kv:(kv[1], kv[0]), reverse=True)
        print("用户1偏好为：{}".format(user_feature_dict))
        order_item_sim, itemSimlarity = self.calSimilarity(user_features, n_items, top_k)
        return order_item_sim, itemSimlarity


if __name__ == "__main__":
    data_path = os.path.dirname(os.getcwd())

    # 评分矩阵
    ratingMatrixPath = os.path.join(data_path, r'data\processedData\ratingmatrix.csv')
    rating_Matrix = pd.read_csv(open(ratingMatrixPath, encoding='utf-8'))

    # item内容特征矩阵
    itemContentMatrixPath = os.path.join(data_path, r'data\processedData\moviesContent.csv')
    itemContentMatrix = pd.read_csv(open(itemContentMatrixPath, encoding='utf-8'))

    # item名
    itemsNamesList = list(rating_Matrix.columns[1:])  # ratingMatrix第0列是用户列，从1列开始是item列

    # 所有item的特征矩阵
    itemsFeatures = itemContentMatrix.values[:, 1:]  # itemContentMatrix第0列是item名列，其余是item的特征分布

    # 调用基于内容的推荐算法
    content_rec = ContentBased(itemsNamesList, itemsFeatures, rating_Matrix.values[:, 1:][0])

    # 结果展示
    print("第一个用户观看过的影片：{}".format([item.split(";")[0] for item, rating in zip(itemsNamesList, rating_Matrix.values[:, 1:][0] > 0) if rating == True])) # 展示用户过去历史行为
    order_item_sim, itemSimlarity = content_rec.getRecItems(10)
    print("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++")
    print("基于内容的推荐算法推荐结果：") # 展示推荐结果
    for index in order_item_sim:
        print('%s,%f' % (itemsNamesList[index], itemSimlarity[index]))